// CDN_APP_Configuration.h
//
// Copyright (c) 2011 LRUnit, LLC All Rights Reserved.
// Author : Kim McKinley
//
// This product includes software developed by :
//  LRUnit, LLC
//
// This software is under the terms of the MIT License :
/*
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
*/

#if !defined(CDN_APP_Configuration)
#define CDN_APP_Configuration

/// Name of the default configuration file
#define DEFAULT_CONFIG_FILE "Default.ini"

namespace cdn {
  namespace app {

// **********************************************************************
/// Handles the configuration parameters
class Configuration
{
public:

/// Load a basic config so we can at least run
//  Load a value in each of the configuration options in case that option
//   happens to be missing from the configuration file.
void CoreConfiguration()
{
  // [Default] section of configuration
  gConfig["DEFAULT"]["SERVER_PORT"] = "7777";
  gConfig["DEFAULT"]["DOMAIN"] = "http://www.example.com";
  gConfig["DEFAULT"]["XML_PROLOG_COMMENT"] = "http://www.example.com";
  gConfig["DEFAULT"]["ALLOW_CREATE_DB"] = "Y";
  gConfig["DEFAULT"]["MAX_CONCURRENT_REQUESTS"] = "20";
  gConfig["DEFAULT"]["SECONDS_TO_WAIT_ON_SHUTDOWN"] = "15";
  gConfig["DEFAULT"]["DATABASE_DIRECTORY"] = "db";
  gConfig["DEFAULT"]["WEB_DIRECTORY"] = "www";
  gConfig["DEFAULT"]["CONFIRM_BEFORE_SHUTDOWN"] = "Y";
  gConfig["DEFAULT"]["PROMPT"] = "Scullery> ";
  gConfig["DEFAULT"]["SERVER_NAME"] = "Scullery Server";

  // Some common file extensions for the [Mime] section
  gConfig["MIME"]["PNG"] = "image/png";
  gConfig["MIME"]["JPG"] = "image/jpg";
  gConfig["MIME"]["JPEG"] = "image/jpeg";
  gConfig["MIME"]["ICO"] = "image/png";
  gConfig["MIME"]["JS"] = "application/javascript";
  gConfig["MIME"]["CSS"] = "text/css";
  gConfig["MIME"]["HTM"] = "text/html";
  gConfig["MIME"]["HTML"] = "text/html";
}

// Helpful config comments
void InsertConfigComments(std::ofstream &defaultini)
{
  defaultini << 
    "; Scullery Server configuration."  << std::endl <<
    "; These are the default settings - edit them to suit your needs" << std::endl <<
    ";  then execute 'SculleryServer.exe' which uses this file by default" << std::endl <<
    ";  or execute 'SculleryServer.exe filename' to use a different config file." << std::endl <<
    std::endl <<
    "; The [DEFAULT] section contains the following parameters :" << std::endl <<
    std::left << 
    std::setw(30) << "; SERVER_PORT"  << "Port to listen for web requests" << std::endl << 
    std::setw(30) << "; SERVER_NAME"  << "Name of server - placed in all responses" << std::endl << 
    std::setw(30) << "; DOMAIN" << "Web domain that will be displayed as a comment in XML output" << std::endl <<
    std::setw(30) << "; XML_PROLOG_COMMENT" << "Free form text displayed as comment in XML output" << std::endl <<
    std::setw(30) << "; ALLOW_CREATE_DB" << "Allow to automatically create a database if requested db not present" << std::endl <<
    std::setw(30) << "; MAX_CONCURRENT_REQUESTS" << "Number of web requests that can be executed concurrently" << std::endl <<
    std::setw(30) << "; SECONDS_TO_WAIT_ON_SHUTDOWN" << "How long to give active requests to complete when asked to shutdown" << std::endl <<
    std::setw(30) << "; DATABASE_DIRECTORY" << "Parent directory for SQLite databases"  << std::endl <<
    std::setw(30) << "; WEB_DIRECTORY" << "Parent directory for web sites"  << std::endl <<
    std::setw(30) << "; CONFIRM_BEFORE_SHUTDOWN" << "Ask via console for confirmation that is OK to shutdown" << std::endl <<
    std::setw(30) << "; PROMPT" << "Scullery Server console prompt"  << std::endl <<
    std::endl <<
    ";  The [MIME] section contains the content-type to send to a client browser when it requests" << std::endl <<
    ";  a file.  The last extension of the filename (the part after the '.') is used to determine" << std::endl <<
    ";  the content-type.  If not in the list, a content-type of 'text/plain' will be used." << std::endl <<
    ";  Only a few are listed here.  Google 'list of mime types' for a list of many more of them." << std::endl <<
    ";  If there is a duplicate extension, the last entry will be used." << std::endl <<
    std::endl;
}

/// When creating a default config file - need to ask for the listening port
void AskForListenPort()
{
  std::cout <<
    "Sadly, a default configuration can not be found." << std::endl <<
    "Will create one, but need to know the port to listen for web requests." << std::endl << std::endl;

  char c, newport[10]; memset(newport,0,10);
  while ( atoi(newport) == 0 )
  {
    std::cout << "TcpIp port to listen for web requests (" << gConfig["DEFAULT"]["SERVER_PORT"] << ") ? ";

    // Look at the first char to see if they want the default, ie: pressed 'enter'
    std::cin.get(c);
    if (c == '\n')
      break;

    // They typed in a new port number - get the rest of it
    *newport = c;
    std::cin >> (newport+1);

    // See if valid number entered
    if ( atoi(newport) == 0 )
    {
      std::cout << "Sorry, but that is an invalid port number. Let's try again." << std::endl;
      continue;
    }

    gConfig["DEFAULT"]["SERVER_PORT"] = newport;
  }
}

/// When no default config file - make one with default values
//  Called by LoadConfiguration() when the default ini file is not found.
void CreateDefaultIni(const std::string DefaultCfgFile = "")
{
  // Get the port the server is to listen for requests
  AskForListenPort();

  // Create the default ini file
  std::ofstream defaultini(DefaultCfgFile.c_str(), std::fstream::out);

  // Insert helpful comments into the config file
  InsertConfigComments(defaultini);

  // The standard settings have already been loaded by LoadConfiguration()
  //  so we will use those settings
  std::map< std::string, options >::iterator s;
  options::iterator c;
  for ( s = gConfig.begin(); s != gConfig.end(); ++s )
  {
    defaultini << "[" << s->first << "]" << std::endl;
    for (c = s->second.begin(); c != s->second.end(); ++c )
      defaultini << "   " << c->first << " = " << c->second << std::endl;
    defaultini << std::endl;
  }

  // Spew some verbage about testing new server and how to change config settings
  std::cout << std::endl <<
    "To test your newly installed Scullery Server enter in a browser : " << std::endl <<
    "     http://127.0.0.1:" << gConfig["DEFAULT"]["SERVER_PORT"] << "/scullery/index.html" << std::endl <<
    "  or '.test' at the Scullery> command prompt." << std::endl << std::endl <<
    "To change the configuration settings edit the '" << DefaultCfgFile << "' file " << std::endl <<
    "  that is in the same directory as SculleryServer.exe" << std::endl <<
    " - enjoy!" << std::endl << std::endl;
}

/// Parse configuration parameters from an .ini file
//  Admittably, this parser assumes a reasonably sane file in ini format
void ParseConfiguation(std::ifstream &configfilestr)
{
  char buffer[1000];
  std::string section;

  // Load the configuration
  while ( !configfilestr.getline(buffer, 1000).eof() )
  {
    char *buf = buffer;
    std::string option, value;
    std::string::reverse_iterator r;

    while ( isspace(*buf) ) buf++;                                // trim leading white space
    if ( *buf == ';' || *buf == '#' || *buf == 0x00 ) continue;   // comment

    // Sections are in the format of [SectionName]
    if ( *buf == '[' )                                            // get config section
    {
      section = ""; // Clear the existing section name
      // Get the section name making uppercase as we go
      for ( buf++; *buf != ']' && *buf != 0x00; buf++ ) section += toupper(*buf);
      // Trim trailing whitespace from the section
      while ( r=section.rbegin(), r != section.rend() && isspace(*r) ) section.erase(section.size()-1,1);
      continue;
    }

    // Get the option name making uppercase as we go
    for ( ; *buf && *buf != '='; buf++) option += toupper(*buf);  // get config option
    if  (*buf == '=' ) *buf++;                                    // skip over the equal sign
    // Trim trailing whitespace from the option
    while ( r=option.rbegin(), r != option.rend() && isspace(*r) ) option.erase(option.size()-1,1);

    // Get the value - will trim leading whitespace, but leave the rest of the string as is
    while ( isspace(*buf) ) buf++;                                // trim leading white space from value
    for ( ; *buf; buf++) value += *buf;                           // get the value for this option

    if (section == "" || option == "" || value == "" ) continue;  // is not a valid config section/option/value
    gConfig[section][option] = value;
  }
}


/// Loads the configuration from an ini file
bool LoadConfiguration(const std::string Filename = "")
{
  // Load core configuration parameters
  CoreConfiguration();

  // The configuration files
  std::string ConfigFile = Filename;
  std::string DefaultCfgFile = DEFAULT_CONFIG_FILE;

  if ( ConfigFile == "") ConfigFile = DefaultCfgFile;

  // If no config file given then use default
  while (true)
  {
    if ( ConfigFile == DefaultCfgFile )
    {
      std::ifstream defaultfilestr(DefaultCfgFile.c_str(), std::fstream::in);

      // Oops - no default config file, probably a new install - create one
      if ( !defaultfilestr )
        CreateDefaultIni(DefaultCfgFile);
    }

    // Open the configuration file
    std::ifstream configfilestr(ConfigFile.c_str(), std::fstream::in);

    // Parse options from the config file
    if ( configfilestr )
    {
      ParseConfiguation(configfilestr);
    }
    else // Config file is broken :(
    {
      // Have we tried the default file?
      if ( ConfigFile != DefaultCfgFile )
      {
        std::cout << "Unable to open configuration file '" << ConfigFile << "'. Using '" << DefaultCfgFile << "' instead." << std::endl;
        // If requested file not the default one - then try to use the default one
        ConfigFile = DefaultCfgFile;
        continue;
      }

      // Even the default file failed - use the internal settings
      std::cout << "Unable to load configuration from a file! - will use internal configuration." << std::endl;
      ConfigFile = "(internal configuation settings)";
    }

  // Insure that we always break from the while( true ) loop
    break;
  }

  // Remember the current config file name
  gConfig["CONFIG"]["FILENAME"] = ConfigFile;

  return true;
}

}; // class Configuration

} // namespace app
} // namespace cdn


#endif
